Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avoid data race on m_pStream #13899

Merged
merged 2 commits into from
Nov 30, 2024
Merged

Conversation

m0dB
Copy link
Contributor

@m0dB m0dB commented Nov 17, 2024

This fixes several tsan warnings related with stopping / closing portaudio streams, including #13870.

Apart from access to a non-atomic variable m_pStream, the real problem here was that
readProcess() writeProcess() would take a copy of m_pStream:

PaStream* pStream = m_pStream;

and close() would then call Pa_StopStream(m_pStream); Apparently stopping a stream and still accessing it in the callback leads to data races, at least on macOS.

Now, it the portaudio docs I read:

Pa_StopStream() is designed to make sure that the buffers you've processed in your callback are all played, which may cause some delay. Alternatively, you could call Pa_AbortStream(). On some platforms, aborting the stream is much faster and may cause some data processed by your callback not to be played.
Another way to stop the stream is to return either paComplete, or paAbort from your callback. paComplete ensures that the last buffer is played whereas paAbort stops the stream as soon as possible. If you stop the stream using this technique, you will need to call Pa_StopStream() before starting the stream again.

So what I do: in close() I change the (atomic) return value for readProcess and writeProcess from paContinue to paAbort, which will stop the stream. Pa_StopStream can then safely be called and m_pStream set to null.

As a side effect this also addresses a comment in the code about a failed intent to use Pa_AbortStream instead of PaStopStream.

This works well on macOS and I have not seen any portaudio related tsan warnings anymore. But since the platform specific aspects of Portaudio it is important to ensure this works as expected on Windows and Linux.

@daschuer
Copy link
Member

m_pStream is meant to be an atomic. I think we can use the null state of this pointer instead of the additional atomic

PaStream* volatile m_pStream;

@@ -820,7 +856,9 @@ int SoundDevicePortAudio::callbackProcessDrift(
//qDebug() << "callbackProcess read:" << (float)readAvailable / outChunkSize << "Buffer empty";
}
}
return paContinue;
return m_streamState.load(std::memory_order_acquire) == StreamState::STOP
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used for syncing. Does relax also work? We may avoid the condition to store the return value directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I initially stored the return value in an atomic, but IIRC I needed the extra state when starting the streams. But I don't remember so I will revise this.

@m0dB
Copy link
Contributor Author

m0dB commented Nov 30, 2024

I have made m_pStream atomic and now store the callback return value as atomic directly. Definitely simpler, and it fixes the issues just as well! Thanks for suggesting this, @daschuer

@m0dB
Copy link
Contributor Author

m0dB commented Nov 30, 2024

It would also be nice to know if this solves the bugs on linux and windows related with opening and closing device streams, e.g when waking up from sleep and unplugging devices.

I am okay with postponing other tsan fixes to post 2.5, but I believe this one should go in. It addresses not only atomic access, but also avoids closing a stream which is still in use in a callback, which seems needlessly risky to me (and the reported data races deep down coreaudio calls show that!)

Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Thank you. Yes this is a bug fix that belongs to 2.5.

@daschuer daschuer merged commit 1a0bb0e into mixxxdj:2.5 Nov 30, 2024
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants